package com.jeremy.comutils.utils;

import com.jeremy.comutils.domain.ComParamConfig;
import com.jeremy.comutils.exception.CustomComException;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

/**
 * 串口参数的配置 串口一般有如下参数可以在该串口打开以前进行配置： 包括串口号，波特率，输入/输出流控制，数据位数，停止位和奇偶校验。
 */
// 注：串口操作类一定要继承SerialPortEventListener
public class SerialPortUtils implements SerialPortEventListener {
    //初始化LOG对象
    private static final Logger logger = LogManager.getLogger();

    // 检测系统中可用的通讯端口类
    private CommPortIdentifier commPortId;

    // 枚举类型
    private Enumeration<CommPortIdentifier> portList;

    // RS232串口
    private SerialPort serialPort;

    // 输入流
    private InputStream inputStream;

    // 输出流
    private OutputStream outputStream;

    // 保存串口返回信息（字符串）
    private StringBuilder data = new StringBuilder();

    // 保存串口返回信息（字节数组）
    private byte[] dataByte = new byte[0];

    private long startTime, endTime;

    /**
     * 初始化串口
     *
     * @throws CustomComException
     * @author Jeremy
     * @param: comParamConfig  存放串口连接必要参数的对象（会在下方给出类代码）
     * @return: void
     */
    public void init(ComParamConfig comParamConfig) throws CustomComException {

        //创建输入扫描对象
        Scanner scanner = new Scanner(System.in);

        while (true) {
            // 获取系统中所有的通讯端口
            portList = CommPortIdentifier.getPortIdentifiers();

            //如果config中不包含端口，就请求输入端口
            if (comParamConfig.getSerialNumber() == null) {
                System.out.print("请输入测试端口号（只需输入数字即可）：");
                comParamConfig.setSerialNumber("COM" + scanner.nextLine());
            }
            // 记录是否含有指定串口
            boolean isExsist = false;
            // 循环通讯端口
            while (portList.hasMoreElements()) {
                commPortId = portList.nextElement();
                // 判断是否是串口
                if (commPortId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                    // 比较串口名称是否是指定串口
                    if (comParamConfig.getSerialNumber().equals(commPortId.getName())) {
                        // 串口存在
                        isExsist = true;
                        // 打开串口
                        try {
                            // open:（应用程序名【随意命名】，阻塞时等待的毫秒数）
                            serialPort = (SerialPort) commPortId.open(Object.class.getSimpleName(), 5000);
                            // 设置串口监听
                            serialPort.addEventListener(this);
                            // 设置串口数据时间有效(可监听)
                            serialPort.notifyOnDataAvailable(true);
                            // 设置串口通讯参数:波特率，数据位，停止位,校验方式
                            serialPort.setSerialPortParams(comParamConfig.getBaudRate(), comParamConfig.getDataBit(),
                                    comParamConfig.getStopBit(), comParamConfig.getCheckoutBit());
                            inputStream = serialPort.getInputStream();
                        } catch (PortInUseException e) {
                            throw new CustomComException("--端口被占用--");
                        } catch (TooManyListenersException e) {
                            throw new CustomComException("--监听器过多--");
                        } catch (UnsupportedCommOperationException e) {
                            throw new CustomComException("--不支持的COMM端口操作异常--");
                        } catch (IOException e) {
                            throw new CustomComException("--读取串口数据时发生IO异常--");
                        }

                        // 结束循环
                        break;
                    }
                }
            }
            // 若不存在该串口则抛出异常
            if (!isExsist) {
                System.out.print("输入端口有误，请重新输入测试端口：");
                comParamConfig.setSerialNumber("COM" + scanner.nextLine());
            } else {
                break;
            }
        }
    }

    /**
     * 实现接口SerialPortEventListener中的方法 读取从串口中接收的数据
     */
    @Override
    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.BI: // 通讯中断
            case SerialPortEvent.OE: // 溢位错误
            case SerialPortEvent.FE: // 帧错误
            case SerialPortEvent.PE: // 奇偶校验错误
            case SerialPortEvent.CD: // 载波检测
            case SerialPortEvent.CTS: // 清除发送
            case SerialPortEvent.DSR: // 数据设备准备好
            case SerialPortEvent.RI: // 响铃侦测
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 输出缓冲区已清空
                break;
            case SerialPortEvent.DATA_AVAILABLE: // 有数据到达
                // 调用读取数据的方法
                try {
                    readComm();
                } catch (CustomComException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }

    /**
     * 读取串口返回信息
     *
     * @author Jeremy
     * @return: String
     */
    public void readComm() throws CustomComException {
        try {

            // 通过输入流对象的available方法获取数组字节长度
            byte[] readBuffer = new byte[inputStream.available()];
            // 从线路上读取数据流
            int len = 0;
            if ((len = inputStream.read(readBuffer)) != -1) {
                // 直接获取到的数据
                dataByte = CommonMethods.byteMerger(dataByte, readBuffer);
                String tmp = new String(readBuffer, 0, len);
                data.append(tmp);
                String stringDataTmp = data.toString();
                endTime = System.currentTimeMillis();
            }
        } catch (IOException e) {
            throw new CustomComException("--读取串口数据时发生IO异常--");
        }
    }

    /**
     * 发送信息到串口(普通字符串)
     *
     * @author Jeremy
     * @param: data
     * @return: void
     */
    public void sendComm(String data) throws CustomComException {
        byte[] writerBuffer = null;
        try {
            writerBuffer = data.getBytes();
            logger.trace("发→◇" + data);
        } catch (NumberFormatException e) {
            throw new CustomComException("--命令格式错误--");
        }
        try {
            outputStream = serialPort.getOutputStream();
            outputStream.write(writerBuffer);
            startTime = System.currentTimeMillis();
            outputStream.flush();
        } catch (NullPointerException e) {
            throw new CustomComException("--找不到串口--");
        } catch (IOException e) {
            throw new CustomComException("--发送信息到串口时发生IO异常--");
        }
    }

    /**
     * 发送信息到串口(十六进制字符串)
     *
     * @author Jeremy
     * @param: data
     * @return: void
     */
    public void sendCommHEX(String data) throws CustomComException {
        byte[] writerBuffer = null;
        try {
            writerBuffer = CommonMethods.hexToByteArray(data);
        } catch (NumberFormatException e) {
            throw new CustomComException("--命令格式错误--");
        }
        try {
            outputStream = serialPort.getOutputStream();
            outputStream.write(writerBuffer);
            logger.trace("【发送十六进制数据：】" + data);
            startTime = System.currentTimeMillis();
            outputStream.flush();
        } catch (NullPointerException e) {
            throw new CustomComException("--找不到串口--");
        } catch (IOException e) {
            throw new CustomComException("--发送信息到串口时发生IO异常--");
        }
    }

    /**
     * 关闭串口
     *
     * @author Jeremy
     * @return: void
     */
    public void closeSerialPort() throws CustomComException {
        if (serialPort != null) {
            serialPort.notifyOnDataAvailable(false);
            serialPort.removeEventListener();
            if (inputStream != null) {
                try {
                    inputStream.close();
                    inputStream = null;
                } catch (IOException e) {
                    throw new CustomComException("--关闭输入流时发生IO异常--");
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                    outputStream = null;
                } catch (IOException e) {
                    throw new CustomComException("--关闭输出流时发生IO异常--");
                }
            }
            serialPort.close();
            serialPort = null;
        }
    }


    /**
     * 串口返回值获取(字符串)
     */
    public byte[] getData() {
        logger.trace("收←◆" + data);
        if (endTime < startTime) {
            endTime = System.currentTimeMillis();
        }
        long time = endTime - startTime;
        logger.trace("【命令返回耗时：" + time + "ms】");
//        if (time > 10000L) {
//            timeOutTimes++;
//            logger.error("【10S过去了,命令返回超时,超时次数：" + timeOutTimes + "】");
//        }
        // 返回执行结果
        return dataByte;
    }

    /*
     * 用来给测试类通知串口操作对象读取缓存已完成，可以重置缓存对象了
     * */
    public void jeremyNotify() {
        data = new StringBuilder();
        dataByte = new byte[0];
    }
}
